#include "apdu.h"
#include "channel.h"

static uint8_t _calc_checksum(const apdu_body_t *body) {
    uint8_t cs = 0;

    cs += body->ctl._val;
    cs += body->addr;

    return cs;
}

int apdu_size(const apdu_t *apdu) {
    if (NULL == apdu) { return 0; }

    if (APDU_BOF_FIX == apdu->bof) {
        return sizeof(apdu_fixed_t);
    } else if (APDU_BOF_VAR == apdu->bof) {
        return sizeof(apdu_head_variable_t) + apdu->var.head.len + sizeof(apdu_tail_t);
    } else {
        return 0;
    }
}

_Bool apdu_check_cs_fixed(const apdu_fixed_t *apdu) {
    uint8_t cs = _calc_checksum(&apdu->body);

    return cs == apdu->tail.cs;
}

_Bool apdu_check_cs_variable(const apdu_variable_t *apdu, const uint8_t *data, int len) {
    int i;
    uint8_t cs = _calc_checksum(&apdu->body);

    for (i = 0; i < len; ++i) {
        cs += data[i];
    }

    return cs == apdu->p_tail->cs;
}

_Bool apdu_check_fixed(const apdu_fixed_t *apdu) {
    return (APDU_BOF_FIX == apdu->head.bof)
           && (APDU_EOF == apdu->tail.eof)
           && apdu_check_cs_fixed(apdu);
}

_Bool apdu_check_variable(apdu_variable_t *apdu) {
    if ((APDU_BOF_VAR != apdu->head.bof) || (APDU_BOF_VAR != apdu->head.bof1))
        return false;

    if (apdu->head.len != apdu->head.len1)
        return false;

    apdu->p_tail = (apdu_tail_t *) &apdu->data[apdu->head.len - 2];

    if (APDU_EOF != apdu->p_tail->eof)
        return false;

    return apdu_check_cs_variable(apdu, apdu->data, apdu->head.len - 2);
}

ssize_t apdu_read(channel_t *channel, apdu_t *apdu) {
    ssize_t rc;
    void *ptr = apdu;

    rc = channel_read(channel, ptr, 1);
    if (rc < 0) { return rc; }
    ptr += rc;

    if (APDU_BOF_FIX == apdu->bof) {
        rc = channel_read(channel, ptr, sizeof(apdu_fixed_t) - 1);
        if (rc < 0) { return rc; }
        ptr += rc;
    } else if (APDU_BOF_VAR == apdu->bof) {
        rc = channel_read(channel, ptr, sizeof(apdu_head_variable_t) - 1);
        if (rc < 0) { return rc; }
        ptr += rc;

        rc = channel_read(channel, ptr, apdu->var.head.len + sizeof(apdu_tail_t));
        if (rc < 0) { return rc; }
        ptr += rc;
    }

    return ptr - (void *) apdu;
}

ssize_t apdu_write(channel_t *channel, const apdu_t *apdu) {
    int nbytes = apdu_size(apdu);

    return channel_write(channel, apdu, nbytes);
}